﻿namespace Mercury.ParticleEngine
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// Contains common mathematical functions.
    /// </summary>
    public static class Calculator
    {
        /// <summary>
        /// Constant representing the value of π.
        /// </summary>
        public const Single Pi = 3.141593f;

        /// <summary>
        /// Constant representing the value of π.
        /// </summary>
// ReSharper disable InconsistentNaming
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1303:ConstFieldNamesMustBeginWithUpperCaseLetter", Justification = "Lower-case symbol denotes the constant value. The capital letter Π has a completely different mathematical meaning.")]
        public const Single π = Pi;
// ReSharper restore InconsistentNaming

        /// <summary>
        /// Constant representing the value of τ (2π).
        /// </summary>
        public const Single Tau = 6.283185f;

        /// <summary>
        /// Constant representing the value of τ (2π).
        /// </summary>
// ReSharper disable InconsistentNaming
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1303:ConstFieldNamesMustBeginWithUpperCaseLetter", Justification = "Lower-case symbol denotes the constant value. The capital letter T has no mathematical meaning.")]
        public const Single τ = Tau;
// ReSharper restore InconsistentNaming

        /// <summary>
        /// Clamps a value so that it falls within a specified range.
        /// </summary>
        /// <param name="value">The value to clamp.</param>
        /// <param name="lower">The minimum permissable value (inclusive).</param>
        /// <param name="upper">The maximum permissable value (inclusive).</param>
        /// <returns>The value, clamped to be within the specified range.</returns>
        static public Single Clamp(Single value, Single lower, Single upper)
        {
            var output = value;

            output = (output < lower) ? lower : output;
            output = (output > upper) ? upper : output;

            return output;
        }

        /// <summary>
        /// Clamps a value so that is falls within a specified range.
        /// </summary>
        /// <param name="value">The value to clamp.</param>
        /// <param name="interval">An interval defining the range of permissable values.</param>
        /// <returns>The value, clamped to be within the specified range.</returns>
        static public Single Clamp(Single value, IntervalF interval)
        {
            var output = value;

            output = (output < interval.X) ? interval.X : output;
            output = (output > interval.Y) ? interval.Y : output;

            return output;
        }

        /// <summary>
        /// Wraps a value to within a specified range.
        /// </summary>
        /// <param name="value">The value to be wrapped.</param>
        /// <param name="lower">The lower value.</param>
        /// <param name="upper">The upper value.</param>
        /// <returns>The wrapped value.</returns>
        static public Single Wrap(Single value, Single lower, Single upper)
        {
            return ((value - lower) % (upper - lower)) + lower;
        }

        /// <summary>
        /// Wraps a value to be within a specified range.
        /// </summary>
        /// <param name="value">The value to be wrapped.</param>
        /// <param name="interval">An interval defining the range of allowable values.</param>
        /// <returns>The wrapped value.</returns>
        static public Single Wrap(Single value, IntervalF interval)
        {
            return ((value - interval.X) % (interval.Y - interval.X)) + interval.X;
        }

        /// <summary>
        /// Calculates the smallest possible epsilon value that avoids floating point rounding
        /// errors when added to the specified value.
        /// </summary>
        /// <param name="value">The value to calculate epsilon for.</param>
        /// <returns>The smallest possible epsilon value that can be added to <paramref name="value"/>
        /// without causing floating point rounding errors.</returns>
        static public Single CalculateEpsilon(Single value)
        {
            var step = 0.000000001f;

            while (value.Equals(value + step))
            {
                step *= 10f;
            }

            return step;
        }

        /// <summary>
        /// Interpolates between two values using a linear curve.
        /// </summary>
        /// <param name="value1">Source value.</param>
        /// <param name="value2">Source value.</param>
        /// <param name="amount">A value between zero and one indicating the weight of <paramref name="value2"/></param>
        /// <returns>The interpolated value.</returns>
        static public Single LinearInterpolate(Single value1, Single value2, Single amount)
        {
            return value1 + ((value2 - value1) * amount);
        }

        /// <summary>
        /// Interpolates between two values using a linear curve.
        /// </summary>
        /// <param name="interval">An interval structure defining the minimum and maximum values.</param>
        /// <param name="amount">A value between zero and one indicating the weight of the maximum value.</param>
        /// <returns>The interpolated value.</returns>
        static public Single LinearInterpolate(IntervalF interval, Single amount)
        {
            return interval.X + ((interval.Y - interval.X) * amount);
        }

        /// <summary>
        /// Interpolates between three values using a linear curve, where the position of the middle source value is variable.
        /// </summary>
        /// <param name="value1">Source value.</param>
        /// <param name="value2">Source value.</param>
        /// <param name="value2Position">The position of the second source value between zero and one.</param>
        /// <param name="value3">Source value.</param>
        /// <param name="amount">A value between zero and one indicating the position on the curve to evaluate.</param>
        /// <returns>The Interpolated value.</returns>
        static public Single LinearInterpolate(Single value1, Single value2, Single value2Position, Single value3, Single amount)
        {
            if (amount < value2Position)
                return LinearInterpolate(value1, value2, amount / value2Position);

            return LinearInterpolate(value2, value3, (amount - value2Position) / (1f - value2Position));
        }

        /// <summary>
        /// Interpolates between two values using a cubic equation.
        /// </summary>
        /// <param name="value1">Source value.</param>
        /// <param name="value2">Source value.</param>
        /// <param name="amount">A value between zero and one indicating the weight of <paramref name="value2"/></param>
        /// <returns>The Interpolated value.</returns>
        static public Single CubicInterpolate(Single value1, Single value2, Single amount)
        {
            amount = Clamp(amount, 0f, 1f);

            return LinearInterpolate(value1, value2, (amount * amount) * (3f - (2f * amount)));
        }

        /// <summary>
        /// Interpolates between two values using a cubic equation.
        /// </summary>
        /// <param name="interval">An interval structure defining the minimum and maximum values.</param>
        /// <param name="amount">A value between zero and one indicating the position on the curve to evaluate.</param>
        /// <returns>The Interpolated value.</returns>
        static public Single CubicInterpolate(IntervalF interval, Single amount)
        {
            return LinearInterpolate(interval.X, interval.Y, (amount * amount) * (3f - (2f * amount)));
        }

        /// <summary>
        /// Returns the greater of two values.
        /// </summary>
        /// <param name="value1">Source value.</param>
        /// <param name="value2">Source value.</param>
        /// <returns>The greater value.</returns>
        static public Single Max(Single value1, Single value2)
        {
            return value1 >= value2 ? value1 : value2;
        }

        /// <summary>
        /// Returns the greater of two values.
        /// </summary>
        /// <param name="value1">Source value.</param>
        /// <param name="value2">Source value.</param>
        /// <returns>The greater value.</returns>
        static public Int32 Max(Int32 value1, Int32 value2)
        {
            return value1 >= value2 ? value1 : value2;
        }

        /// <summary>
        /// Returns the lesser of two values.
        /// </summary>
        /// <param name="value1">Source value.</param>
        /// <param name="value2">Source value.</param>
        /// <returns>The lesser value.</returns>
        static public Single Min(Single value1, Single value2)
        {
            return value1 <= value2 ? value1 : value2;
        }

        /// <summary>
        /// Returns the lesser of two values.
        /// </summary>
        /// <param name="value1">Source value.</param>
        /// <param name="value2">Source value.</param>
        /// <returns>The lesser value.</returns>
        static public Int32 Min(Int32 value1, Int32 value2)
        {
            return value1 <= value2 ? value1 : value2;
        }

        /// <summary>
        /// Returns the absolute value of a single precision Single point value.
        /// </summary>
        /// <param name="value">Source value.</param>
        /// <returns>The absolute value of <paramref name="value"/>.</returns>
        static public Single Absolute(Single value)
        {
            return value >= 0f ? value : -value;
        }

        /// <summary>
        /// Returns the absolute value of an integer.
        /// </summary>
        /// <param name="value">Source value.</param>
        /// <returns>The absolute value of <paramref name="value"/>.</returns>
        static public Int32 Absolute(Int32 value)
        {
            return value >= 0 ? value : -value;
        }

        /// <summary>
        /// Returns the angle whose cosine is the specified value.
        /// </summary>
        /// <param name="value">A number representing a cosine.</param>
        /// <returns>The angle whose cosine is the specified value.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Acos(Single value)
        {
            return (Single)Math.Acos(value);
        }

        /// <summary>
        /// Returns the angle whose sine is the specified value.
        /// </summary>
        /// <param name="value">A number representing a sine.</param>
        /// <returns>The angle whose sine is the specified value.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Asin(Single value)
        {
            return (Single)Math.Asin(value);
        }

        /// <summary>
        /// Returns the angle whos tangent is the speicified number.
        /// </summary>
        /// <param name="value">A number representing a tangent.</param>
        /// <returns>The angle whos tangent is the speicified number.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Atan(Single value)
        {
            return (Single)Math.Atan(value);
        }

        /// <summary>
        /// Returns the angle whose tangent is the quotient of the two specified numbers.
        /// </summary>
        /// <param name="y">The y coordinate of a point.</param>
        /// <param name="x">The x coordinate of a point.</param>
        /// <returns>The angle whose tangent is the quotient of the two specified numbers.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Atan2(Single y, Single x)
        {
            return (Single)Math.Atan2(y, x);
        }

        /// <summary>
        /// Returns the sine of the specified angle.
        /// </summary>
        /// <param name="value">An angle specified in radians.</param>
        /// <returns>The sine of the specified angle.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Sin(Single value)
        {
            return (Single)Math.Sin(value);
        }

        /// <summary>
        /// Returns the hyperbolic sine of the specified angle.
        /// </summary>
        /// <param name="value">An angle specified in radians.</param>
        /// <returns>The hyperbolic sine of the specified angle.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Sinh(Single value)
        {
            return (Single)Math.Sinh(value);
        }

        /// <summary>
        /// Returns the cosine of the specified angle.
        /// </summary>
        /// <param name="value">An angle specified in radians.</param>
        /// <returns>The cosine of the specified angle.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Cos(Single value)
        {
            return (Single)Math.Cos(value);
        }

        /// <summary>
        /// Returns the hyperbolic cosine of the specified angle.
        /// </summary>
        /// <param name="value">An angle specified in radians.</param>
        /// <returns>The hyperbolic cosine of the specified angle.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Cosh(Single value)
        {
            return (Single)Math.Cosh(value);
        }

        /// <summary>
        /// Returns the tangent of the specified angle.
        /// </summary>
        /// <param name="value">An angle specified in radians.</param>
        /// <returns>The tangent of the specified angle.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Tan(Single value)
        {
            return (Single)Math.Tan(value);
        }

        /// <summary>
        /// Returns the hyperbolic tangent of the specified angle.
        /// </summary>
        /// <param name="value">An angle specified in radians.</param>
        /// <returns>The hyperbolic tangent of the specified angle.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Tanh(Single value)
        {
            return (Single)Math.Tanh(value);
        }

        /// <summary>
        /// Returns the natural (base e) logarithm of the specified value.
        /// </summary>
        /// <param name="value">A number whose logarithm is to be found.</param>
        /// <returns>The natural (base e) logarithm of the specified value.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Log(Single value)
        {
            return (Single)Math.Log(value);
        }

        /// <summary>
        /// Returns the specified value raised to the specified power.
        /// </summary>
        /// <param name="value">Source value.</param>
        /// <param name="power">A single precision floating point number that specifies a power.</param>
        /// <returns>The specified value raised to the specified power.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single Raise(Single value, Single power)
        {
            return (Single)Math.Pow(value, power);
        }

        /// <summary>
        /// Returns the square root of the specified value.
        /// </summary>
        /// <param name="value">Source value.</param>
        /// <returns>The square root of the specified value.</returns>
        [DebuggerStepThrough]
        [ExcludeFromCodeCoverage]
        static public Single SquareRoot(Single value)
        {
            return (Single)Math.Sqrt(value);
        }
    }
}